package cn.edu.cuit.elena.nati.shell;

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.lang.reflect.Field;
import java.util.LinkedList;
import java.util.List;

import cn.edu.cuit.elena.common.Outputable;
import cn.edu.cuit.elena.trace.Tracer;

public abstract class Shell
    implements Outputable
{
    /**
     * Use to output.
     * */
    private volatile Thread errThread;

    protected String[] command;
    protected File workDir;
    protected String[] envs;

    private Process unixProcess;
    protected int exitCode;
    protected boolean complete;

    protected long startTime;
    protected long endTime;

    public Shell(String[] command, String workPath, String[] envs)
    {
        this.command = command;
        if( workPath != null )
        {
            workDir = new File( workPath );
        }
        this.envs = envs;
    }

    protected void runCommand() throws IOException
    {
        if( getExecString() == null )
        {
            throw new NullPointerException( "Command is NULL!" );
        }

        startTime = System.nanoTime();
        if( getExecString().length == 1 )
        {
            unixProcess = Runtime.getRuntime().exec( getExecString()[0], envs, workDir );
        }
        else
        {
            unixProcess = Runtime.getRuntime().exec( getExecString(), envs, workDir );
        }

        //        if( timeOutInterval > 0 )
        //        {
        //            timeOutTimer = new Timer();
        //            timeoutTimerTask = new ShellTimeoutTimerTask( this );
        //            //One time scheduling.
        //            timeOutTimer.schedule( timeoutTimerTask, timeOutInterval );
        //        }
        final BufferedReader errReader = new BufferedReader( new InputStreamReader( unixProcess.getErrorStream() ) );
        BufferedReader inReader = new BufferedReader( new InputStreamReader( unixProcess.getInputStream() ) );
        final StringBuffer errMsg = new StringBuffer();

        // read error and input streams as this would free up the buffers
        // free the error stream buffer
        errThread = new Thread()
        {
            List<String> buffer = new LinkedList<String>();

            @Override
            public void run()
            {
                try
                {
                    String line = errReader.readLine();
                    while( ( line != null ) && !isInterrupted() )
                    {
                        errMsg.append( line );
                        errMsg.append( System.getProperty( "line.separator" ) );
                        line = errReader.readLine();
                        Tracer.debugTrace( Shell.class,line, null );
                        buffer.add( line );
                    }
                }
                catch( IOException ioe )
                {
                    Tracer.fatalTrace( Shell.class, "Shell error thread exception : Error reading the error stream",
                        ioe );
                }
            }
        };

        try
        {
            errThread.start();
        }
        catch( IllegalStateException ise )
        {
        }
        try
        {
            parseExecResult( inReader ); // parse the output
            // clear the input stream buffer
            String line = inReader.readLine();
            while( line != null )
            {
                line = inReader.readLine();
            }

            // wait for the process to finish and check the exit code
            exitCode = unixProcess.waitFor();
            try
            {
                // make sure that the error thread exits
                errThread.join();
            }
            catch( InterruptedException ie )
            {
                Tracer.fatalTrace( Shell.class, "Shell exception :Interrupted while reading the error stream", ie );
            }
            complete = true;
            //the timeout thread handling
            //taken care in finally block
            //            if( exitCode != 0 )
            //            {
            //                throw new ExitCodeException( exitCode, errMsg.toString() );
            //            }
        }
        catch( InterruptedException ie )
        {
            throw new IOException( ie.toString() );
        }
        finally
        {
            try
            {
                inReader.close();
            }
            catch( IOException ioe )
            {
                Tracer.fatalTrace( Shell.class, "Error while closing the input stream", ioe );
            }
            if( complete == false )
            {
                errThread.interrupt();
            }
            try
            {
                errReader.close();
            }
            catch( IOException ioe )
            {
                Tracer.fatalTrace( Shell.class, "Error while closing the error stream", ioe );
            }
            unixProcess.destroy();

            endTime = System.nanoTime();
        }
    }

    /** return an array containing the command name & its parameters */
    protected abstract String[] getExecString();

    /** Parse the execution result */
    protected abstract void parseExecResult( BufferedReader lines ) throws IOException;

    protected void setWorkingDirectory( String dir )
    {
        workDir = new File( dir );
    }

    protected void setEnvironment( String[] envs )
    {
        this.envs = envs;
    }

    protected void setCommand( String... command )
    {
        this.command = command;
    }

    public String[] getOutput()
    {
        if( errThread == null )
        {
            return null;
        }

        try
        {
            Field f = errThread.getClass().getDeclaredField( "buffer" );
            @SuppressWarnings( "unchecked" )
            List<String> list = (List<String>) f.get( errThread );
            String[] lines = new String[list.size()];
            for( int index = 0; index < list.size(); index++ )
            {
                lines[index] = list.get( index );
            }

            return lines;
        }
        catch( SecurityException e )
        {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        catch( NoSuchFieldException e )
        {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        catch( IllegalArgumentException e )
        {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        catch( IllegalAccessException e )
        {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

        return null;
    }
}
